﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.IO;
using System.Text.RegularExpressions;
using System.Security.Cryptography;
using System.Diagnostics;

namespace GoodStuff.Net
{
    /// <summary>
    /// This class enables you to connect to a pop3 server and retrieve all the messages
    /// </summary>
    public class PopClient : IDisposable
    {
        private TcpClient _client;
        private StreamReader _reader;
        private StreamWriter _writer;

        public PopClient()
        {     
            //GNDN
        }

        public bool AuthenticateWithAPOP { get; set; }

        public void Connect(string server, int port, string username, string password)
        {
            _client = new TcpClient();
            _client.Connect(server, port);

            NetworkStream stream = _client.GetStream();
            _client.ReceiveTimeout = (int)TimeSpan.FromSeconds(5).TotalMilliseconds;
            _reader = new StreamReader(stream, Encoding.ASCII);
            _writer = new StreamWriter(stream, Encoding.ASCII);

            //sign in.
            string challenge = ValidateResponse();

            //find the apop key <xyz@domain.com> in the message
            Match m = Regex.Match(challenge, @"<.*>");
            string key = m.Value;

            if (AuthenticateWithAPOP)
            {
                Send("APOP {0} {1}", username, GetMD5(key + password));
                ValidateResponse();
            }
            else
            {
                Send("USER {0}", username);
                ValidateResponse();

                Send("PASS {0}", password);
                ValidateResponse();
            }
        }

        private string ValidateResponse()
        {
            string message = Receive();
            if (!message.StartsWith("+OK"))
            {
                throw new Pop3Exception(message);
            }
            return message;
        }

        private void Send(string message, params object[] arguments)
        {
            message = string.Format(message, arguments);
            _writer.WriteLine(message);
            _writer.Flush();

            Trace.WriteLine(message);
        }

        private string Receive()
        {
            string result = _reader.ReadLine();
            Trace.WriteLine(result);
            return result;
        }

        private string GetMD5(string inputString)
        {
            byte[] input = Encoding.UTF8.GetBytes(inputString);
            byte[] output = MD5.Create().ComputeHash(input);
            StringBuilder sb = new StringBuilder();
            foreach (byte b in output)
            {
                sb.Append(b.ToString("x2"));
            }

            return sb.ToString();
        }

        public void Dispose()
        {
            if (_reader != null)
            {
                _reader.Close();
                _reader = null;

            }

            if (_writer != null)
            {
                _writer.Close();
                _writer = null;

            }

            if (_client != null)
            {
                _client.Close();
                _client = null;
            }
        }

        public IEnumerable<int> GetMessageList()
        {
            //assume authenticated
            Send("LIST");
            string receive = Receive();
            if (receive.StartsWith("+OK"))
            {
                //+OK 2 messages (320 octets)
                //int numberOfMessages = int.Parse(receive[1]);
                while ((receive = Receive()) != ".")
                {
                    string[] info = receive.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
                    yield return int.Parse(info[0]);
                };
            }            
        }
    }

    public class Pop3Exception : Exception
    {
        public Pop3Exception(string message):base(message)
        {

        }
    }
}
